package com.xh.shuati.aop;


import com.xh.shuati.annotation.RateLimiter;
import com.xh.shuati.blackfilter.BlackIpUtils;
import com.xh.shuati.common.ErrorCode;
import com.xh.shuati.exception.BusinessException;
import com.xh.shuati.exception.ThrowUtils;
import com.xh.shuati.model.entity.User;
import com.xh.shuati.model.enums.LimitTypeEnum;
import com.xh.shuati.model.enums.RateIntercvalEnum;
import com.xh.shuati.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.concurrent.TimeUnit;

/**
 * 防止爬虫AOP切面
 */
@Aspect
@Component
@Slf4j
public class RateLimitAspect {

    /**
     * 封禁档次
     */
    private static long temporaryBanTime = 30 * 60 * 1000L;

    /**
     * Redis根据IP前缀
     */
    private static final String LimiterIpPrefix = "limit:ip:";
    private static final String LimiterIpCountPrefix = "IP:count:";

    private static final String LimiterUserPrefix = "limit:user:";
    private static final String LimiterUserCountPrefix = "User:count:";

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private HttpServletRequest request;


    /**
     * 固定时间窗口限流方法
     * @param joinPoint
     * @param rateLimiter
     * @return
     * @throws Throwable
     */
    //
    @Around("@annotation(rateLimiter)")
    public Object rateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
        String limitType = rateLimiter.limitType().getLimitType();
        //1.获取封禁类型
        String key = null;
        String count_key = null;
        if(limitType.equals(LimitTypeEnum.REJECT_IP.getLimitType())){
            String ip = getClientIp();
            ThrowUtils.throwIf(key.equals("unknown_ip"), ErrorCode.OPERATION_ERROR,"IP地址异常,请咨询管理员");
            key = LimiterIpPrefix + ip;
            count_key = LimiterIpCountPrefix + key;
        }
        if(limitType.equals(LimitTypeEnum.REJECT_USER.getLimitType())){
            Long userId = getUserId();
            key = LimiterUserPrefix + userId;
            count_key = LimiterUserCountPrefix + key;
        }
        TimeUnit timeUnit = RateIntercvalEnum.toTimeUnit(rateLimiter.timeUnit());
        //2.获分布式锁(可重入)
        boolean tryLock = false;
        RLock rLock = redissonClient.getLock(key);
        RAtomicLong atomicLong = redissonClient.getAtomicLong(count_key);
        try {
            tryLock = rLock.tryLock(1, 5, TimeUnit.SECONDS);
            if(tryLock){
                //3.获取当前访问次数
                long currentCount = atomicLong.getAndIncrement();
                if(currentCount == 0){
                    atomicLong.expire(rateLimiter.CountTime(), timeUnit);
                }
                //4.触发警告
                if(currentCount == rateLimiter.WarnCount()){
                    //警告
                    throw new BusinessException(110,  "警告！访问太频繁");
                }
                //5.触发限流,封禁账号
                if(currentCount >= rateLimiter.LimitCount()){
                    User loginUser = userService.getLoginUser(request);
                    if(ObjectUtils.isEmpty(loginUser)){
                        //未登录直接封禁IP
                        BlackIpUtils.addBlackIp(getClientIp());
                    }else{
                        //将用户设置为账号封禁
                        loginUser.setUserRole("ban");
                        userService.updateById(loginUser);
                        //将用户踢下线
                        userService.userLogout(request);
                    }
                    log.warn("反爬虫触发 -,key:{},IP:{},user:{}", key,getClientIp(),getUserId());
                    throw new BusinessException(ErrorCode.NOT_VISIT);
                }
            }else{
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"系统繁忙请稍后重试");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (Exception e){
            throw e;
        }finally {
            //6.Todo 踩坑笔记:释放锁(必须判断是否持有锁并且是当前线程持有的锁,否则会抛出IllegalMonitorStateException异常)
            if ( tryLock && rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }
        //Todo 是否可以优化,将封禁信息提交给管理员审核,如果封禁添加到Nacos加入BloomFilter
        return joinPoint.proceed();
    }

    /**
     * 获取客户端IP
     */
    private String getClientIp() {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 多个代理时取第一个IP
        return StringUtils.isNotEmpty(ip) ? ip.split(",")[0] : "unknown_ip";
    }

    /**
     * 获取用户ID
     */
    private Long getUserId() {
        try {
            // 从token或session中获取用户ID
            String token = request.getHeader("Authorization");
            HttpSession session = request.getSession(false);
            if (session != null) {
                return (Long) session.getAttribute("userId");
            }
        } catch (Exception e) {
            log.error("获取用户ID失败", e);
        }
        return 0L;
    }
}
